package impl

import (
	"bytes"
	"context"
	"errors"
	"gitee.com/goddesszz/gocommon/myutil"
	"github.com/qiniu/qmgo"
	"github.com/samber/lo"
	"github.com/sirupsen/logrus"
	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/extension"
	"github.com/yuin/goldmark/renderer/html"
	"go.mongodb.org/mongo-driver/mongo"
	"html/template"
	"monoblog/common"
	"monoblog/common/register"
	"monoblog/config"
	"monoblog/domain/dao"
	"monoblog/domain/dto"
	"monoblog/domain/model"
	"monoblog/domain/service"
	"strconv"
	"time"
)

func init() {
	register.RegisterInstance[service.ArticleService]("ArticleService", func() service.ArticleService {
		return &ArticleServiceImpl{
			articleRepository:  dao.NewArticleRepository(),
			categoryRepository: dao.NewCategoryRepository(),
			sectionRepository:  dao.NewSectionRepository(),
			markdown: goldmark.New(
				goldmark.WithExtensions(
					extension.GFM, // 支持 GitHub Flavored Markdown（表格、任务列表等）
					extension.Footnote,
				),
				goldmark.WithRendererOptions(
					html.WithUnsafe(),
				),
			),
		} // 返回唯一实例
	})
}

type ArticleServiceImpl struct {
	articleRepository  *dao.ArticleRepository
	categoryRepository *dao.CategoryRepository
	sectionRepository  *dao.SectionRepository
	markdown           goldmark.Markdown
}

func (s *ArticleServiceImpl) Add(ctx context.Context, form dto.ArticleReqForm) (string, error) {
	article := &model.Article{
		Title:        form.Title,
		AuthorId:     form.LoginUser.UID,
		AuthorName:   form.LoginUser.Name,
		AuthorAvatar: form.LoginUser.Avatar,
		Status:       model.DRAFT.Value(),
		Content:      form.Content,
		//ShowText:     form.ShowText,
		//Photo:        form.Photo,
		DirId:      form.DirId,
		CreateTime: time.Now(),
	}

	//判断是否存在
	exists, _ := s.articleRepository.ExistsByTitle("", article.Title, article.AuthorId)
	if exists {
		return "", errors.New("article name is duplicated")
	}

	err := s.articleRepository.Add(article)
	if err != nil {
		logrus.Errorf("[aid: %s] Add article failed: %v", ctx.Value("aid"), err.Error())
		return "", err
	}
	logrus.Infof("[aid: %s] Add article success: %v", ctx.Value("aid"), article.ID)
	return article.ID, nil
}

func (s *ArticleServiceImpl) UpdateByID(ctx context.Context, id string, form dto.ArticleReqForm) error {
	article := &model.Article{
		ID:      id,
		Title:   form.Title,
		Content: form.Content,
		//ShowText:   form.ShowText,
		//Photo:      form.Photo,
		DirId:      form.DirId,
		UpdateTime: time.Now(), //更新时间
	}

	//判断是否存在
	exists, _ := s.articleRepository.ExistsByTitle(id, article.Title, article.AuthorId)
	if exists {
		return errors.New("article name is duplicated")
	}

	err := s.articleRepository.UpdateByID(id, *article)
	return err
}

func (s *ArticleServiceImpl) DeleteByID(ctx context.Context, id string) error {
	err := s.articleRepository.DeleteByID(id)       //删除article
	_ = s.articleRepository.DeleteBlogStatsByID(id) //删除blogStats

	return err
}

func (s *ArticleServiceImpl) Publish(ctx context.Context, id string, form dto.ArticleReqPublishForm) error {

	errChan := make(chan error)

	go func(ctx context.Context) {
		status, err := s.articleRepository.GetStatusByID(id)
		if err != nil {
			logrus.Errorf("[aid: %s] GetStatusByID failed.  %v", ctx.Value("aid"), err.Error())
			errChan <- err
			return
		}
		if status == 0 || status == model.DRAFT.Value() { //==0是为了兼容
			//第一次发布时，添加blogStats
			if e := s.articleRepository.AddBlogStats(id); qmgo.IsDup(e) {
				logrus.Warnf("[aid: %s] Add BlogStats时 主键重复. id: %v", ctx.Value("aid"), id)
			}
		}
		errChan <- nil
	}(ctx)

	// 创建channel用于接收并行查询结果
	categoryChan := make(chan *model.Category)
	sectionChan := make(chan *model.Section)

	go func(categoryId string) {
		if categoryId == "" {
			categoryChan <- &model.Category{}
			return
		}

		category, err0 := s.categoryRepository.FindByID(categoryId)
		if err0 != nil {
			logrus.Errorf("[aid: %s] 查询category failed.  %v", ctx.Value("aid"), err0.Error())
			categoryChan <- nil
		} else {
			categoryChan <- category
		}

	}(form.CategoryId)

	go func(sectionId string) {
		if sectionId == "" {
			sectionChan <- &model.Section{}
			return
		}

		section, err0 := s.sectionRepository.FindByID(sectionId)
		if err0 != nil {
			logrus.Errorf("[aid: %s] 查询section failed.  %v", ctx.Value("aid"), err0.Error())
			sectionChan <- nil
		} else {
			sectionChan <- section
		}
	}(form.SectionId)

	// 等待查询结果并处理错误
	category := <-categoryChan
	if category == nil {
		return errors.New("查询category failed")
	}
	section := <-sectionChan
	if section == nil {
		return errors.New("查询section failed")
	}

	err := <-errChan
	if err != nil {
		return err
	}

	//更新文章数据
	article := &model.Article{
		ID:           id,
		Title:        form.Title,
		CategoryId:   category.ID,
		CategoryName: category.Name,
		SectionId:    section.ID,
		SectionName:  section.Name,
		Status:       model.PUBLISHED.Value(),
		ShowText:     form.ShowText,
		Photo:        form.Photo,
		Tags:         form.Tags,
		PublishTime:  lo.ToPtr(time.Now()), //发布时间
	}
	//更新
	err = s.articleRepository.UpdateByID(id, *article)
	if err == nil {
		logrus.Infof("[aid: %s] When publish, blog update success.  blogId:%v", ctx.Value("aid"), article.ID)
		go func() {
			stats, _ := s.articleRepository.FindBlogStats(id)
			article.BlogStats = stats
			err2 := service.GetSearchService().PushData(ctx, article)
			if err2 != nil {
				logrus.Errorf("[aid: %s] When publish, pushData failed.  blogId:%v", ctx.Value("aid"), err2.Error())
				return
			} else {
				logrus.Infof("[aid: %s] When publish, pushData success.  blogId:%v", ctx.Value("aid"), article.ID)
			}

			service.GetMomentPublishService().PublishWithBlog(ctx, article.AuthorId, article.ID)
		}()
	} else {
		logrus.Errorf("[aid: %s] When publish, blog update failed.  %v", ctx.Value("aid"), err.Error())
	}
	return err
}

func (s *ArticleServiceImpl) FindByID(ctx context.Context, id string) (*model.Article, error) {
	article, err := s.articleRepository.FindByID(id)
	if errors.Is(err, mongo.ErrNoDocuments) {
		return nil, errors.New("article not found")
	}
	return article, err
}

func (s *ArticleServiceImpl) FindInBlogIds(ctx context.Context, ids []string) ([]*model.Article, error) {
	if len(ids) == 0 {
		return []*model.Article{}, nil
	}

	query := dto.ArticleQuery{
		Ids: ids,
	}
	articles, _, err := s.articleRepository.FindList(query)
	if err != nil {
		logrus.Errorf("[aid: %s] FindInBlogIds failed.  %v", ctx.Value("aid"), err.Error())
		return nil, err
	}
	err = s.fillBogStats(ctx, articles)
	return articles, err
}

func (s *ArticleServiceImpl) FindListPage(ctx context.Context, query dto.ArticleQuery) ([]*model.Article, error) {
	articles, _, err := s.articleRepository.FindList(query)

	return articles, err
}

func (s *ArticleServiceImpl) FindListByUser(ctx context.Context, userId string, query common.BaseQuery) ([]*model.Article, error) {
	if userId == "" {
		return []*model.Article{}, nil
	}

	return s.FindListPage(ctx, dto.ArticleQuery{
		BaseQuery: query,
		AuthorId:  userId,
	})
}

// BlogsByUser 用户的文章列表(已经发布的)
func (s *ArticleServiceImpl) BlogsByUser(ctx context.Context, userId string, query common.BaseQuery) ([]*model.Article, error) {
	if userId == "" {
		return []*model.Article{}, nil
	}

	list, err := s.FindListPage(ctx, dto.ArticleQuery{
		BaseQuery: query,
		AuthorId:  userId,
		Status:    model.PUBLISHED.Value(),
	})

	err = s.fillBogStats(ctx, list)
	return list, err
}

// BlogsByCategory 分类下的文章列表
func (s *ArticleServiceImpl) BlogsByCategory(ctx context.Context, cateId string, query common.BaseQuery) ([]*model.Article, error) {
	if cateId == "" {
		return []*model.Article{}, nil
	}

	list, err := s.FindListPage(ctx, dto.ArticleQuery{
		BaseQuery:  query,
		CategoryId: cateId,
		Status:     model.PUBLISHED.Value(),
	})

	err = s.fillBogStats(ctx, list)
	return list, err
}

// BlogsBySection 专栏下的文章列表
func (s *ArticleServiceImpl) BlogsBySection(ctx context.Context, sectionId string, query common.BaseQuery) ([]*model.Article, error) {
	if sectionId == "" {
		return []*model.Article{}, nil
	}
	list, err := s.FindListPage(ctx, dto.ArticleQuery{
		BaseQuery: query,
		SectionId: sectionId,
		Status:    model.PUBLISHED.Value(),
	})
	err = s.fillBogStats(ctx, list)
	return list, err
}

// PopularBlogs 首页推荐文章列表
func (s *ArticleServiceImpl) PopularBlogs(ctx context.Context, query common.BaseQuery) ([]*model.Article, error) {
	list, err := s.FindListPage(ctx, dto.ArticleQuery{
		BaseQuery: query,
		Status:    model.PUBLISHED.Value(),
	})
	err = s.fillBogStats(ctx, list)
	return list, err
}

func (s *ArticleServiceImpl) fillBogStats(ctx context.Context, list []*model.Article) error {
	filter := lo.Filter(list, func(it *model.Article, index int) bool {
		return it.Status == model.PUBLISHED.Value()
	})
	ids := lo.Map(filter, func(it *model.Article, index int) string {
		return it.ID
	})
	if len(ids) == 0 {
		return nil
	}

	//根据文章ID查询blogStats，再将blogStats和article关联
	points, err := s.articleRepository.FindBlogStatsByIds(ids)
	if err != nil {
		logrus.Errorf("[aid: %s] FindBlogStatsByIds failed. err = %v", ctx.Value("aid"), err.Error())
		return err
	}
	//用map存储BlogStats，key为ArticleId（即BlogStats.ID）
	pointMap := lo.SliceToMap(points, func(p *model.BlogStats) (string, *model.BlogStats) {
		return p.ID, p
	})

	for _, article := range list {
		article.BlogStats = pointMap[article.ID]
	}
	return nil
}

// InterestedBlogs 你可能感兴趣的文章
func (s *ArticleServiceImpl) InterestedBlogs(ctx context.Context, userId string) ([]*model.Article, error) {
	var articles []*model.Article
	// TODO 待完成
	return articles, nil
}

// ReadById 阅读文章 不能直接查库，要优先查询缓存
func (s *ArticleServiceImpl) ReadById(ctx context.Context, id string, preview bool) (*model.Article, error) {
	//预览模式：不需要检查是否已发布，不需要点击量+1
	if !preview {
		if exists, _ := service.GetSearchService().ExistSearch(ctx, id); !exists {
			return nil, errors.New("publish_list 中没有查询到此文章 id=" + id)
		}

		go func() { //点击量+1
			err0 := s.articleRepository.UpdateBlogClickCount(id, 1)
			if err0 != nil {
				logrus.Errorf("[aid: %s] UpdateBlogClickCount 时出错. err = %v", ctx.Value("aid"), err0.Error())
			}
			// 将文章id加入等待更新队列
			config.GetRedisClient().SAdd(context.Background(), common.REDIS_KEY_WAIT_FOR_UPDATE_BLOG, id)
		}()
	}

	//从数据库查询文章详情
	article, err := s.articleRepository.FindByID(id)
	if err != nil {
		return nil, err
	}

	if point, err1 := s.articleRepository.FindBlogStats(id); err1 == nil {
		article.BlogStats = point
	}
	// 渲染文章内容
	article.RenderedContent = template.HTML(s.renderMarkdown(ctx, article.Content))

	return article, err
}

func (s *ArticleServiceImpl) PushDataToSearch(ctx context.Context, blogIds []string) ([]string, error) {
	if len(blogIds) == 0 {
		return nil, errors.New("blogIds是空的")
	}

	blogs, err := s.FindInBlogIds(ctx, blogIds)
	if err != nil {
		logrus.Errorf("[aid: %s] PushDataToSearch时, FindInBlogIds failed. err = %v", ctx.Value("aid"), err.Error())
		return nil, err
	}
	var successIds []string
	for _, blog := range blogs {
		if blog.Status == model.PUBLISHED.Value() {
			err2 := service.GetSearchService().PushData(ctx, blog)
			if err2 != nil {
				logrus.Errorf("[aid: %s] When publish, pushData failed.  %v", ctx.Value("aid"), err2.Error())
			} else {
				logrus.Infof("[aid: %s] When publish, pushData success.  %v", ctx.Value("aid"), blog.ID)
				successIds = append(successIds, blog.ID)
			}
		}
	}

	return successIds, nil
}

// renderMarkdown 渲染Markdown为Html文本
func (s *ArticleServiceImpl) renderMarkdown(ctx context.Context, content string) string {
	if content == "" {
		return ""
	}
	var buf bytes.Buffer
	if err := s.markdown.Convert([]byte(content), &buf); err != nil {
		logrus.Errorf("[aid: %s] 渲染Markdown失败. err = %v", ctx.Value("aid"), err.Error())
		return ""
	}
	return buf.String()
}

// 计算文章ID的索引
func index(id string) string {
	return strconv.Itoa(myutil.HashCode(id) % 10)
}
